

#include "levelmanager.h"
#include "resourcemanager.h"
#include "gamemanager.h"
#include "gamedatamanager.h"
#include "strutil.h"
#include "messagequeue.h"
#include "enemy.h"
#include "gameplayscene.h"

LevelManager* LevelManager::_instance = nullptr;

LevelManager* LevelManager::getInstance() {
	if (_instance == nullptr) { 
		_instance = new LevelManager();
	}
	return _instance;
}

LevelManager::LevelManager() : 
	initialGoldQuantity(0)
	, enemyWaveIndex(0)
	, gamePlayScene(nullptr)
	, _waveStartDelayTicks(0.0f)
	, _ticks(0.0f)
	, firstWaveStarted(false)
{
	holderName = "level_manager";   
}

void LevelManager::init() {
}

void LevelManager::loadLevel(const char *fileName){
	enemyWaveIndex = 0;
	enemyIndex = 0;

	_waveStartDelayTicks = 0.0f;
	_previousEnemyTicks = 0.0f;
	_ticks = 0.0f;

	hasMoreWave = true;

	sceneEntities.clear();
	enemyWaves.clear();
	ways.clear();
	towers.clear();
	mines.clear();
	bboxes.clear();
	heroes.clear();

	string xml = ResourceManager::loadTextFile(fileName);

	using namespace tinyxml2;
	tinyxml2::XMLDocument doc;
	doc.Parse(xml.c_str());

	XMLElement* root = doc.FirstChildElement("data");

	XMLElement* xeBg = root->FirstChildElement("bg");
	sceneBackgroundName = xeBg->GetText();


	XMLElement* xeEntities = root->FirstChildElement("entities");
	XMLElement* item = xeEntities->FirstChildElement("i");

	//load items
	while(item){
		SceneEntityData ent;
		ent.x = item->IntAttribute("x");
		ent.y = item->IntAttribute("y");
		ent.type = item->Attribute("t");
		ent.resourceName = item->Attribute("k");
		sceneEntities.push_back(ent);

		item = item->NextSiblingElement("i");
	}

	// load way data
	XMLElement* xeWays = root->FirstChildElement("ways");
	XMLElement* xeWay = xeWays->FirstChildElement("way");

	while(xeWay){
		WayInfo wi;

		XMLElement* xePoint = xeWay->FirstChildElement("i");
		while(xePoint){
			int y = xePoint->IntAttribute("y");

			Point pt = Point(xePoint->IntAttribute("x"), GameManager::BASE_HEIGHT - y);
			wi.waypoints.push_back(pt);
			wi.arrRestDistance.push_back(0);

			xePoint = xePoint->NextSiblingElement("i");
		}

		ways.push_back(wi);

		xeWay = xeWay->NextSiblingElement("way");
	}

	// calculate rest distance of each way point
	int n = ways.size();
	for(int i = 0; i < n; i++) {
		WayInfo& wi = ways[i];

		vector<Point>& arrWayPoints = wi.waypoints;
		vector<float>& arrDistance = wi.arrRestDistance;

		if(arrWayPoints.size() < 2) {
			continue;
		}

		float totalDist = 0;
		for(int j = arrWayPoints.size() - 2; j >= 0; j--) {
			float dist = (arrWayPoints[j]).getDistance(arrWayPoints[j + 1]);
			totalDist += dist;
			arrDistance[j] = totalDist;
		}
	}

	// load enemies data
	XMLElement* xeEnemies = root->FirstChildElement("enemies");
	XMLElement* xeWave = xeEnemies->FirstChildElement("wave");
	while(xeWave){
		WaveInfo wi;
		wi.delay = xeWave->IntAttribute("delay") / 1000.0;
		wi.hasBoss = xeWave->BoolAttribute("has_boss");

		XMLElement* xeItem = xeWave->FirstChildElement("i");
		while(xeItem){
			EnemyInfo ei;
			ei.delay = xeItem->IntAttribute("d") / 1000.0;
			ei.unitName = xeItem->Attribute("t");
			ei.level = xeItem->IntAttribute("l");
			ei.wayIndex = xeItem->IntAttribute("w");
			ei.nextWaypointIndex = xeItem->IntAttribute("way_point");
			const char* str = xeItem->Attribute("birth_pos");
			if(str) {
				string s = str;
				vector<string> arr = strutil::split(s, ",");
				ei.birthPos = Point(atoi(arr[0].c_str()), atoi(arr[1].c_str()));
				ei.birthPos.y = GameManager::BASE_HEIGHT - ei.birthPos.y;
			}

			wi.enemies.push_back(ei);

			xeItem = xeItem->NextSiblingElement("i");
		}

		enemyWaves.push_back(wi);
		xeWave = xeWave->NextSiblingElement("wave");
	}

	// load heroes data
	XMLElement* xeHeroes = root->FirstChildElement("heroes");
	if(xeHeroes) {
		XMLElement* xeHero = xeHeroes->FirstChildElement("i");
		while(xeHero){
			UnitInfo info;
			const char* s = xeHero->Attribute("t");
			if(s) {
			}

			info.level = xeHero->IntAttribute("l");
			info.x = atof(xeHero->Attribute("x"));
			info.y = GameManager::BASE_HEIGHT - atof(xeHero->Attribute("y"));
			info.hp = atof(xeHero->Attribute("hp"));
			info.damage = atof(xeHero->Attribute("damage"));
			info.duration = atof(xeHero->Attribute("duration"));
			info.chance = atof(xeHero->Attribute("chance")) * 100.0f;

			heroes.push_back(info);

			xeHero = xeHero->NextSiblingElement("i");
		}
	}

	// load towers data
	XMLElement* xeTowers = root->FirstChildElement("towers");
	XMLElement* xeTower = xeTowers->FirstChildElement("i");

	while(xeTower){
		TowerType type = TowerType::rally_point;
		const char* s = xeTower->Attribute("t");
		if(s && strcmp("castle", s) == 0) {
			type = TowerType::castle;
		}

		int x = xeTower->IntAttribute("x");
		int y = GameManager::BASE_HEIGHT - xeTower->IntAttribute("y");

		towers.push_back(TowerInfo(type, x, y));

		xeTower = xeTower->NextSiblingElement("i");
	}

	// load mines data
	XMLElement* xeMines = root->FirstChildElement("mines");
	XMLElement* xeMine = xeMines->FirstChildElement("i");

	while(xeMine){
		TowerType type = TowerType::mine;
		int x = xeMine->IntAttribute("x");
		int y = GameManager::BASE_HEIGHT - xeMine->IntAttribute("y");

		mines.push_back(TowerInfo(type, x, y));

		xeMine = xeMine->NextSiblingElement("i");
	}
}

bool LevelManager::isPointInRoadArea(const Point& pt) {
	for (size_t i = 0; i < bboxes.size(); i++) {
		Rect rt = bboxes[i];
		if(rt.containsPoint(pt)) {
			return true;
		}
	}

	return false;
}

vector<int> LevelManager::getWaysOfWave(int waveIndex) {
	vector<int> arr;
	if(waveIndex < 0) {
		return arr;
	}

	WaveInfo info = enemyWaves[waveIndex];
	int n = info.enemies.size();

	for(int i = 0; i < n; i++) {
		EnemyInfo ei = info.enemies[i];
		if(find(arr.begin(), arr.end(), ei.wayIndex) == arr.end()) {
			arr.push_back(ei.wayIndex);
		}
	}

	return arr;
}

void LevelManager::update( float ticks )
{
	handleMessage();

	if(firstWaveStarted) {
		spawnEnemy(ticks);
	}
}

void LevelManager::handleMessage()
{
	auto msg = PEEK_MSG(holderName);
	if(msg) {
		switch(msg->key){
		case MessageType::call_next_wave:
			startNextWave();
			break;
		default:break;
		}

		POP_MSG(holderName);
	}
}

void LevelManager::spawnEnemy(float ticks){
	if(enemyWaveIndex < 0 || !hasMoreWave){
		return;
	}

	_ticks += ticks;

	WaveInfo waveInfo = enemyWaves[enemyWaveIndex];

	if(_waveStartDelayTicks < ticks && (_waveStartDelayTicks + ticks) >= waveInfo.delay) {
		SEND_MSG_ARGS(holderName, "all", MessageType::next_wave_began, 0);
	}

	_waveStartDelayTicks += ticks;

	if (_waveStartDelayTicks >= waveInfo.delay) {
		EnemyInfo ei = waveInfo.enemies[enemyIndex];
		float intervals = _ticks - _previousEnemyTicks;
		if (intervals >= ei.delay) {
			createEnemy(ei);

			enemyIndex++;
			_previousEnemyTicks = _ticks;
		}

		if(enemyIndex >= (int)waveInfo.enemies.size()) {
			if((enemyWaveIndex + 1) >= (int)enemyWaves.size()) {
				hasMoreWave = false;
			}

			if(hasMoreWave) {
				SEND_MSG_ARGS(holderName, "ui_layer", MessageType::show_wave_indicator, 0);
				enemyWaveIndex++;
				enemyIndex = 0;

				_waveStartDelayTicks = 0.0f;
			}
		}
	}
}

Enemy* LevelManager::createEnemy(const EnemyInfo& info)
{
	if(info.wayIndex < 0 || info.wayIndex >= (int)ways.size()) {
		log("way index out of range.");
	}

	if(info.unitName == "delay") {
		return nullptr;
	}

	Enemy* enemy = new Enemy();
	WayInfo wi = ways[info.wayIndex];
	enemy->set(info.unitName, wi.waypoints[0], gamePlayScene);
	enemy->wayIndex = info.wayIndex;
	enemy->waypoints = ways[info.wayIndex].waypoints;
	enemy->nextWaypointIndex = 1;

	if(info.nextWaypointIndex > 0) {
		enemy->nextWaypointIndex = info.nextWaypointIndex;
		enemy->position = info.birthPos;
	}

	enemy->moveTo(wi.waypoints[enemy->nextWaypointIndex]);

	gamePlayScene->addEnemy(enemy);

	return enemy;
}

void LevelManager::startNextWave()
{
	firstWaveStarted = true;

	if(!hasMoreWave){
		return;
	}

	WaveInfo waveInfo = enemyWaves[enemyWaveIndex];
	_waveStartDelayTicks = waveInfo.delay;
}

cocos2d::Point LevelManager::getCastlePosition()
{
	Point ret = Point::ZERO;
	for(TowerInfo info : towers) {
		if(info.type == TowerType::castle) {
			ret = Point(info.x, info.y);
			break;
		}
	}

	return ret;
}
